package com.star.mall.base.query;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.star.mall.base.exception.BusinessException;
import com.star.mall.constants.CacheKit;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 查询参数
 * @author StarQ
 * @since 2021-01-23
 */
@Data
@ApiModel(value = "query", description = "分页查询参数")
public class Query<T> implements Serializable {
    private static final long serialVersionUID = -2187346073476283340L;

    @ApiModelProperty(value = "查询字段数组", name = "query")
    private List<Param> query = new ArrayList<>();
    @ApiModelProperty(value = "分页参数", name = "page")
    private Page<T> page;
    @ApiModelProperty(value = "排序字段数组", name = "sorter")
    private List<Sorter> sorter = new ArrayList<>();

    private Class<T> entityClass;

    public QueryWrapper<T> toWrapper(Class<T> clazz) {
        initDbConvertCache(clazz);
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        convertParam(query, wrapper);
        convertSorter(sorter, wrapper);
        return wrapper;
    }

    private void convertSorter(List<Sorter> sorter, QueryWrapper<T> wrapper) {
        sorter.forEach(item -> {
            if (Direction.DESC.equals(item.getDirection())) {
                wrapper.orderByDesc(convert2DbType(item.getField()));
            }else if (Direction.ASC.equals(item.getDirection())){
                wrapper.orderByAsc(convert2DbType(item.getField()));
            }else {
                throw new BusinessException("排序符号不存在");
            }
        });
    }

    private void convertParam(List<Param> params, QueryWrapper<T> wrapper) {
        params.forEach(param -> {
            if (ArrayUtil.isNotEmpty(param.getParams())) {
                if (Relation.AND.equals(param.getRelation())) {
                    wrapper.and(item -> convertParam(param.getParams(), item));
                }else {
                    wrapper.or(item -> convertParam(param.getParams(), item));
                    wrapper.or();
                }
            }else {
                operate(param, wrapper);
            }
        });
    }

    private void operate(Param param, QueryWrapper<T> wrapper) {
        String field = convert2DbType(param.getField());
        switch (param.getOperation()) {
            case EQUAL:
                wrapper.eq(field,param.getValue());
                break;
            case NOT_EQUAL:
                wrapper.ne(field,param.getValue());
                break;
            case IS_NULL:
                wrapper.isNull(field);
                break;
            case NOT_NULL:
                wrapper.isNotNull(field);
                break;
            case LESS_THAN:
                wrapper.le(field,param.getValue());
                break;
            case GREAT_THAN:
                wrapper.ge(field, param.getValue());
                break;
            case LEFT_LIKE:
                wrapper.likeLeft(field,param.getValue());
                break;
            case RIGHT_LIKE:
                wrapper.likeRight(field,param.getValue());
                break;
            case LIKE:
                wrapper.like(field,param.getValue());
                break;
            default:
                break;
        }
        if (ObjectUtil.isNotEmpty(param.getRelation()) && param.getRelation().equals(Relation.OR)) {
            wrapper.or();
        }
    }

    private void initDbConvertCache(Class<T> clazz) {
        this.entityClass = clazz;
        if (!CacheKit.DB_TYPE_CACHE.containsKey(clazz.getName())) {
            Map<String, String> cache = new ConcurrentHashMap<>();
            for (Field field : clazz.getDeclaredFields()) {
                TableField tableField = field.getAnnotation(TableField.class);
                TableId tableId = field.getAnnotation(TableId.class);
                if (ObjectUtil.isNotEmpty(tableField)) {
                    cache.put(field.getName(), tableField.value());
                }
                if (ObjectUtil.isNotEmpty(tableId)) {
                    cache.put(field.getName(), tableField.value());
                }
            }
            CacheKit.DB_TYPE_CACHE.put(clazz.getName(), cache);
        }
    }

    private String convert2DbType(String field) {
        Map<String, String> cache = CacheKit.DB_TYPE_CACHE.get(this.entityClass.getName());
        return cache.getOrDefault(field, field);
    }
}
